package com.demo.core.orm.ibatis;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.sql.DataSource;

import com.demo.core.orm.ibatis.builder.xml.SqlMapConfigParser;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import com.ibatis.common.xml.NodeletException;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.sqlmap.engine.builder.xml.SqlMapParser;
import com.ibatis.sqlmap.engine.builder.xml.XmlParserState;
/**
 * 本地化SqlMapClient
 * 继承{@link SqlMapClientFactoryBean},带有缓存支持自动分页SqlMapClient
 * 如果Dao层实现{@link SearchDaoSupport<T>},则提供分页支持。
 * 同时为分页的缓存可行,进行处理;
 * 并根据不同的数据库切换SqlMap配置文件
 * 
 * @author ryuu.kk
 *
 */
public class DialectSqlMapClientFactoryBean extends SqlMapClientFactoryBean implements InitializingBean {

	/**
	 * oracle数据库
	 */
	public static final int DB_KEY_ORACLE = 1;
	/**
	 * mysql数据库
	 */
	public static final int DB_KEY_MYSQL = 5;
	
	/**
	 * iBatis配置语法分析器类
	 */
	private SqlMapConfigParser configParser;
	
	/**
	 * 数据源
	 */
	private DataSource dataSource;
	
	/**
	 * 本地配置文件数组
	 */
	private Resource[] configLocations;
	
	/**
	 * 本地化配置处理
	 */
	private DBDialectHandler dialectHandler;
	
	/**
	 * Build a SqlMapClient instance based on the given standard configuration.
	 * <p>The default implementation uses the standard iBATIS {@link SqlMapClientBuilder}
	 * API to build a SqlMapClient instance based on an InputStream (if possible,
	 * on iBATIS 2.3 and higher) or on a Reader (on iBATIS up to version 2.2).
	 * @param configLocations the config files to load from
	 * @param properties the SqlMapClient properties (if any)
	 * @return the SqlMapClient instance (never <code>null</code>)
	 * @throws IOException if loading the config file failed
	 * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient
	 */
	@Override
	protected SqlMapClient buildSqlMapClient(
			Resource[] configLocations, Resource[] mappingLocations, Properties properties)
			throws IOException {
		Assert.notNull(this.configParser, "ConfigParser can not null");
		
		if (ObjectUtils.isEmpty(configLocations)) {
			throw new IllegalArgumentException("At least 1 'configLocation' entry is required");
		}

		SqlMapClient client = null;
		
		for (Resource configLocation : configLocations) {
			InputStream is = configLocation.getInputStream();
			try {
				client = configParser.parse(is, properties);
			}
			catch (RuntimeException ex) {
				throw new NestedIOException("Failed to parse config resource: " + configLocation, ex.getCause());
			}
		}

		if (mappingLocations != null) {
			SqlMapParser mapParser = SqlMapParserFactory.createSqlMapParser(configParser);
			//SqlMapParser mapParser = new SqlMapParser(configParser.getState());
			for (Resource mappingLocation : mappingLocations) {
				try {
					mapParser.parse(mappingLocation.getInputStream());
				}
				catch (NodeletException ex) {
					throw new NestedIOException("Failed to parse mapping resource: " + mappingLocation, ex);
				}
			}
		}

		return client;
	}
	/**
	 * Inner class to avoid hard-coded iBATIS 2.3.2 dependency (XmlParserState class).
	 */
	protected static class SqlMapParserFactory {

		public static SqlMapParser createSqlMapParser(SqlMapConfigParser configParser) {
			// Ideally: XmlParserState state = configParser.getState();
			// Should raise an enhancement request with iBATIS...
			XmlParserState state = null;
			try {
				Field stateField = SqlMapConfigParser.class.getDeclaredField("state");
				stateField.setAccessible(true);
				state = (XmlParserState) stateField.get(configParser);
			}
			catch (Exception ex) {
				throw new IllegalStateException("iBATIS 2.3.2 'state' field not found in SqlMapConfigParser class - " +
						"please upgrade to IBATIS 2.3.2 or higher in order to use the new 'mappingLocations' feature. " + ex);
			}
			return new SqlMapParser(state);
		}
	}
	/**
	 * 由Ioc容器注入,动态更改
	 */
	public void setConfigParser(SqlMapConfigParser configParser) {
		this.configParser = configParser;
	}
	
	@Override
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
		super.setDataSource(dataSource);
	}

	/**
	 * 设置本地配置文件资源(sqlMap)
	 */
	@Override
	public void setConfigLocations(Resource[] configLocations) {
		this.configLocations = configLocations;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		
		//根据数据库进行SqlMap指定
		Connection conn = this.dataSource.getConnection();
		DatabaseMetaData dbMetaData = conn.getMetaData();
		String productName = dbMetaData.getDatabaseProductName();
		conn.close();
		if (this.dialectHandler == null) {
			//默认配置处理实现类
			dialectHandler = new DBDialectHandler() {

				@Override
				public int getDbType(String productName) {
					
					if ("mysql".equals(productName.toLowerCase())) {
						return DB_KEY_MYSQL;
					}
					if ("oracle".equals(productName.toLowerCase())) {
						return DB_KEY_ORACLE;
					}
					return 0;
				}
				
				@Override
				public String getDbName(int type) {
					String productName = "";
					switch(type) {
					case 1:
						productName = "oracle";
						break;
					case 5:
						productName = "mysql";
						break;
					default:
						productName = "oracle";
					}
					return productName;
				}
				
				@Override
				public Resource[] rewriterConfigLocations(int type,	Resource[] configLocations) {
					List<Resource> newResource = new ArrayList<Resource>();
					for (Resource res : configLocations) {
						Resource resource = res;
						if (!resource.exists()) {
							String path = "";
							String fileFullName = resource.getFilename();
							String extension = fileFullName.substring(fileFullName.lastIndexOf(".") + 1);
							String fileName = fileFullName.substring(0, fileFullName.length() - extension.length() - 1);
							if (resource instanceof ClassPathResource) {
								path = ((ClassPathResource) resource).getPath();
							}
							if (path.length() < 1) {
								try {
									path = resource.getURL().getPath();
								} catch (IOException e) {
									e.printStackTrace();
								}
							}
							String pathResolver = path.substring(0, path.lastIndexOf("/"));
							
							path = pathResolver + "/" + fileName + "_" +this.getDbName(type) + "." + extension;
							resource = new ClassPathResource(path);
						}
						newResource.add(resource);
					}
					return newResource.toArray(new Resource[]{});
				}
			};
		}
		
		int type = dialectHandler.getDbType(productName);
		
		//根据数据库类型调整本地化配置信息
		super.setConfigLocations(dialectHandler.rewriterConfigLocations(type, configLocations));
		
		//初始化父Factory
		super.afterPropertiesSet();
	}
	
	public void setDialectHandler(DBDialectHandler dialectHandler) {
		this.dialectHandler = dialectHandler;
	}
	/**
	 * 本地化配置处理接口
	 * 取得当前数据库类型
	 * 根据数据库调整本地化数据库配置文件
	 * @author ryuu.kk
	 *
	 */
	public interface DBDialectHandler {
		
		int getDbType(String productName);
		
		String getDbName(int type);
		
		Resource[] rewriterConfigLocations(int type, Resource[] configLocations);
	}
}